
import java.util.*;
import java.io.*;

class Tuple {

    private long downloadTimeMillis;
    private long timestampBefore, timestampAfter;
    private String segmentName;
    private long segmentLength;

    /**
     * @return the timestampBefore
     */
    public long getTimestampBefore() {
        return timestampBefore;
    }

    /**
     * @param timestampBefore the timestampBefore to set
     */
    public void setTimestampBefore(long timestampBefore) {
        this.timestampBefore = timestampBefore;
    }

    /**
     * @return the timestampAfter
     */
    public long getTimestampAfter() {
        return timestampAfter;
    }

    /**
     * @param timestampAfter the timestampAfter to set
     */
    public void setTimestampAfter(long timestampAfter) {
        this.timestampAfter = timestampAfter;
    }

    /**
     * @return the segmentName
     */
    public String getSegmentName() {
        return segmentName;
    }

    /**
     * @param segmentName the segmentName to set
     */
    public void setSegmentName(String segmentName) {
        this.segmentName = segmentName;
    }

    /**
     * @return the segmentLength
     */
    public long getSegmentLength() {
        return segmentLength;
    }

    /**
     * @param segmentLength the segmentLength to set
     */
    public void setSegmentLength(long segmentLength) {
        this.segmentLength = segmentLength;
    }

    public long downloadTime() {
        return timestampAfter - timestampBefore;
    }

    public double throughput() {
        return segmentLength / (downloadTime() / 1000);
    }

    /**
     * @return the downloadTimeMillis
     */
    public long getDownloadTimeMillis() {
        return downloadTimeMillis;
    }

    /**
     * @param downloadTimeMillis the downloadTimeMillis to set
     */
    public void setDownloadTimeMillis(long downloadTimeMillis) {
        this.downloadTimeMillis = downloadTimeMillis;
    }
}

class Client {

    private LinkedList<Tuple> tuples;
    private String name;
    private int numHickups;

    public Client() {
        tuples = new LinkedList<>();
    }

    /**
     * @return the tuples
     */
    public LinkedList<Tuple> getTuples() {
        return tuples;
    }

    /**
     * @param tuples the tuples to set
     */
    public void setTuples(LinkedList<Tuple> tuples) {
        this.tuples = tuples;
    }

    /**
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * @param name the name to set
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * @return the numHickups
     */
    public int getNumHickups() {
        return numHickups;
    }

    /**
     * @param numHickups the numHickups to set
     */
    public void setNumHickups(int numHickups) {
        this.numHickups = numHickups;
    }
}

class TupleComparator implements Comparator<Tuple> {

    @Override
    public int compare(Tuple o1, Tuple o2) {
        return (int) (o1.getDownloadTimeMillis() - o2.getDownloadTimeMillis());
    }
}

public class VideoParser {

    public static void main(String[] args) {
        System.out.println("Parsing " + args[0]);
        try {
            parseVideo(args[0], args[1]);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static double getTotalThroughputForClient(Client client) {
        int numTuples = 0;
        double totalAverages = 0;
        Iterator<Tuple> iterator = client.getTuples().listIterator();
        while (iterator.hasNext()) {
            Tuple tuple = iterator.next();
            numTuples += 1;
            //            System.out.println("Before: "  +tuple.getTimestampBefore() + " After:" + tuple.getTimestampAfter() + " " + ((float)(tuple.getTimestampAfter() - tuple.getTimestampBefore()) / (float) 1000));
            totalAverages += (float) tuple.getSegmentLength() / ((float) (tuple.getTimestampAfter() - tuple.getTimestampBefore()) / (float) 1000);
        }
        return totalAverages / numTuples;
    }

    public static LinkedList<Long> calculateHickups(Client client) {
        LinkedList<Long> delays = new LinkedList<>();
        Iterator<Tuple> iterator = client.getTuples().listIterator();
        Tuple tuple0 = null;
        if (iterator.hasNext()) {
            tuple0 = iterator.next();
        }
        while (iterator.hasNext()) {
            Tuple tuple1 = iterator.next();
            long delay = tuple1.getTimestampAfter() - tuple0.getTimestampAfter();
            if (delay > 20000 - tuple0.downloadTime()) {
                //we have a hickup
                delays.add(delay - (20000 - tuple0.downloadTime()));
            } else {
                delays.add((long)0);
            }
            tuple0 = tuple1;
        }
        return delays;
    }

    public static LinkedList<Long> calculateHickups2(Client client) {
        LinkedList<Long> delays = new LinkedList<>();
        Iterator<Tuple> iterator = client.getTuples().listIterator();
        
        while (iterator.hasNext()) {
            Tuple tuple = iterator.next();
            //StringTokenizer tokenizer = new StringTokenizer(tuple.getSegmentName(), ".");
            //long timestampSegment = Long.parseLong(tokenizer.nextToken());
            //System.out.println("*******" + tuple.downloadTime() + "**********");
            if (tuple.downloadTime() > 10000) {
                //we have a hickup
                delays.add(tuple.downloadTime()- 10000);
            } else {
                delays.add((long)0);
            }
        }
        return delays;
    }
    
    public static LinkedList<Long> calculateHickups3(Client client) {
        LinkedList<Long> delays = new LinkedList<>();
        Iterator<Tuple> iterator = client.getTuples().listIterator();
        Tuple tuple0 = null;
        if (iterator.hasNext()) {
            tuple0 = iterator.next();
        }
        while (iterator.hasNext()) {
            Tuple tuple1 = iterator.next();
            long delay = tuple1.getTimestampAfter() - tuple0.getTimestampAfter();
            if (delay > 10000) {
                //we have a hickup
                delays.add(delay - 10000);
            } else {
                delays.add((long)0);
            }
            tuple0 = tuple1;
        }
        return delays;
    }
    
    public static LinkedList<Tuple> getAllTuplesFromAllClients(LinkedList<Client> clients) {
        LinkedList<Tuple> tuples = new LinkedList<>();
        Iterator<Client> iterator = clients.listIterator();
        while (iterator.hasNext()) {
            Client client = iterator.next();
            Iterator<Tuple> tupleIterator = client.getTuples().listIterator();
            while (tupleIterator.hasNext()) {
                tuples.add(tupleIterator.next());
            }
        }
        return tuples;
    }

    public static void parseVideo(String path, final String fileEndings) throws IOException, FileNotFoundException {
        File folder = new File(path);
        File[] listOfFiles = folder.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith(fileEndings);
            }
        });



        Set<String> clientNamesAlreadyGot = new HashSet<>();
        LinkedList<Client> clients = new LinkedList<>();
        for (File file : listOfFiles) {
            Scanner sc = new Scanner(file);
            String[] tuple; //each line in the tracefile is a tuple.
            int stopCounter = 0;
            while (sc.hasNext()) {
                tuple = sc.nextLine().split(";");
                if (tuple[0].compareToIgnoreCase("hickup") == 0) {
                    Iterator<Client> iterator = clients.listIterator();
                    while (iterator.hasNext()) {
                        Client client = iterator.next();
                        if (client.getName().equals(tuple[1])) {
                            client.setNumHickups(client.getNumHickups() + 1);
                            break;
                        }
                    }
                } else if (tuple.length > 2) {
                    String name = tuple[0];
                    Tuple tupleClient = new Tuple();
                    tupleClient.setDownloadTimeMillis(Long.parseLong(tuple[1]));
                    tupleClient.setTimestampBefore(Long.parseLong(tuple[2]));
                    tupleClient.setTimestampAfter(Long.parseLong(tuple[3]));
                    tupleClient.setSegmentName(tuple[4]);
                    tupleClient.setSegmentLength(Long.parseLong(tuple[5]));
                    if (!clientNamesAlreadyGot.contains(name)) {
                        clientNamesAlreadyGot.add(name);
                        Client client = new Client();
                        client.setName(name);
                        client.getTuples().add(tupleClient);
                        clients.add(client);
                    } else {
                        Iterator<Client> iterator = clients.listIterator();
                        while (iterator.hasNext()) {
                            Client client = iterator.next();
                            if (client.getName().equals(name)) {
                                client.getTuples().add(tupleClient);
                                break;
                            }
                        }
                    }
                }
            }
        }
        //now we should have all the clients and all the tuples for the file
        Iterator<Client> iterator = clients.listIterator();
        while (iterator.hasNext()) {
            Client client = iterator.next();
            System.out.println(client.getName() + ", " + getTotalThroughputForClient(client) + ", Bps");
        }
        iterator = clients.listIterator();
        while (iterator.hasNext()) {
            Client client = iterator.next();
            System.out.println(client.getName() + ",Number of hickups" + "," + calculateHickups3(client).size());
        }


        LinkedList<Tuple> allTuples = getAllTuplesFromAllClients(clients);
        Collections.sort(allTuples, new TupleComparator());
        Iterator<Tuple> tupleIterator = allTuples.listIterator();
        int numForSecond = 0;
        long currentTimeStamp = 0;
        Vector<Long> uniqueTimestamps = new Vector<>();
        Vector<Integer> totalOngoingDownloads = new Vector<>();
        Vector<Integer> downloadsStartedForSecond = new Vector<>();
        Vector<Long> totalOngoingBandwidths = new Vector<>();
        while (tupleIterator.hasNext()) {
            Tuple tuple = tupleIterator.next();
            long timestamp = tuple.getDownloadTimeMillis() / 1000;
            if (currentTimeStamp != timestamp) {
                if (currentTimeStamp != 0) {
                    //System.out.println("CurrentTimestamp," + currentTimeStamp + ",numForSecond," + numForSecond);
                    uniqueTimestamps.add(currentTimeStamp);
                    downloadsStartedForSecond.add(numForSecond);
                    totalOngoingDownloads.add(new Integer(0));
                    totalOngoingBandwidths.add(new Long(0));
                }
                currentTimeStamp = timestamp;
                numForSecond = 1;
            } else {
                numForSecond += 1;
            }
            //System.out.println(tuple.getTimestampBefore()/1000);
        }
        //System.out.println("CurrentTimestamp," + currentTimeStamp + ",numForSecond," + numForSecond);
        uniqueTimestamps.add(currentTimeStamp);
        totalOngoingDownloads.add(new Integer(0));
        totalOngoingBandwidths.add(new Long(0));
        downloadsStartedForSecond.add(numForSecond);
        
        System.out.println("Downloads Started Per Second:");
        for(long stamp=uniqueTimestamps.firstElement();stamp<=uniqueTimestamps.lastElement();stamp++){
            int index = uniqueTimestamps.indexOf(stamp);
            if (index >= 0) {
                System.out.println(stamp + "," + downloadsStartedForSecond.get(index));
            } else {
                System.out.println(stamp + "," + 0);
            }
        }
        //for (long stamp : uniqueTimestamps) {
        tupleIterator = allTuples.listIterator();
        while (tupleIterator.hasNext()) {
            Tuple tuple = tupleIterator.next();
            long downloadTimeMillis = tuple.getDownloadTimeMillis() / 1000;
            long timestampBefore = tuple.getTimestampBefore() / 1000;
            long timestampAfter = tuple.getTimestampAfter() / 1000;
            long bandwidth = (long) (tuple.getSegmentLength() / ((tuple.getTimestampAfter() - tuple.getTimestampBefore()) / (double) 1000));
            //System.out.println("DL time," + (timestampAfter - timestampBefore));
            for (long timestamp = downloadTimeMillis; timestamp < downloadTimeMillis + (timestampAfter - timestampBefore); timestamp++) {
                int index = uniqueTimestamps.indexOf(timestamp);
                if (index >= 0) {
                    totalOngoingDownloads.setElementAt(totalOngoingDownloads.get(index) + 1, index);
                    totalOngoingBandwidths.setElementAt(totalOngoingBandwidths.get(index) + bandwidth, index);
                } else {
                    uniqueTimestamps.insertElementAt(timestamp, uniqueTimestamps.indexOf(timestamp - 1) + 1);
                    totalOngoingDownloads.insertElementAt(1, uniqueTimestamps.indexOf(timestamp - 1) + 1);
                    totalOngoingBandwidths.insertElementAt(bandwidth, uniqueTimestamps.indexOf(timestamp - 1) + 1);
                }
            }
            //            System.out.println("Count:" + count + " " + (timestampAfter-timestampBefore));
        }
        Iterator<Client> clientIterator = clients.listIterator();
        while(clientIterator.hasNext()){
            Client client = clientIterator.next();
            //System.out.println("Download Times for Client: "+ client.getName());
            for(Tuple tuple:client.getTuples()){
                System.out.println("DL time," + (tuple.getTimestampAfter()/1000 - tuple.getTimestampBefore()/1000));
            }
        }
        //}
        System.out.println("Throughput over time: ");
        for (long stamp = uniqueTimestamps.get(0); stamp <= uniqueTimestamps.lastElement(); stamp++) {
            int index = uniqueTimestamps.indexOf(stamp);
            if (index >= 0) {
                System.out.println(stamp + "," + totalOngoingBandwidths.get(index));
            } else {
                System.out.println(stamp + "," + 0);
            }
        }
        System.out.println("Ongoing downloads:");
        for (long stamp = uniqueTimestamps.get(0); stamp <= uniqueTimestamps.lastElement(); stamp++) {
            int index = uniqueTimestamps.indexOf(stamp);
            if (index >= 0) {
                System.out.println(stamp + "," + totalOngoingDownloads.get(index));
            } else {
                System.out.println(stamp + "," + 0);
            }
        }

        Iterator<Client> iteratorClients = clients.listIterator();
        int numOfClientsDelayed = 0;
        int numOfDelays = 0;
        System.out.println("Delays:");
        while (iteratorClients.hasNext()) {
            Client client = iteratorClients.next();
            int totalSegmentsDownloadedClient = client.getTuples().size();
            List<Long> delays = calculateHickups3(client);
            //System.out.println(client.getName() + "," + delays.size());
            //if (delays.size() > 0) {
            //    numOfClientsDelayed++;
            //}
            int numOfDelaysClient = 0;
            for (Long l : delays) {
                if(l>0){
                //System.out.println(l + ",ms");
                numOfDelaysClient++;
                }
            }
            System.out.println(client.getName() + ", delays:" + numOfDelaysClient + ", total:" + totalSegmentsDownloadedClient);
            if(numOfDelaysClient > 0){
                numOfClientsDelayed++;
                numOfDelays+=numOfDelaysClient;
            }
        }
        System.out.println("Number of clients delayed," + numOfClientsDelayed);
        System.out.println("Number of delays," + numOfDelays);
        System.out.println("Total downloads: " + allTuples.size());
    }
}
